package com.wujunhao1024.sqldp.strategy;

import com.wujunhao1024.sqldp.workbench.Workbench;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;

import java.util.List;
import java.util.Optional;

public class SelectStrategy {

    public void handleSelect(Select select, Workbench workbench) {
        handleSelect(select, null, workbench);
    }

    /**
     * <pre>
     *     Select 面临情况如下:
     *     1. PlainSelect 为完整的查询语句
     *     2. SetOperationList 为多个查询语句的集合
     *     3. 其他情况，如通过 () 包含的查询语句，如 (select * from table)
     * </pre>
     *
     * @param select          查询语句（可能包含 UNION 等操作）
     * @param outerAliasTable 上级表别名
     * @param workbench       工作台
     */
    public void handleSelect(Select select, String outerAliasTable, Workbench workbench) {
        if (select instanceof PlainSelect plainSelect) {
            handlePlainSelect(plainSelect, workbench);
        } else if (select instanceof SetOperationList selectList) {
            handleSetOperationList(selectList, workbench);
        } else if (select.getPlainSelect() != null) {
            handlePlainSelect(select.getPlainSelect(), outerAliasTable, workbench);
        }
    }

    private void handlePlainSelect(PlainSelect plainSelect, Workbench workbench) {
        handlePlainSelect(plainSelect, null, workbench);
    }

    /**
     * <pre>
     *     PlainSelect 为完整的查询语句，需要考虑的情况如下:
     *     1. SelectItems 为查询的字段列表
     *     2. FromItem 为 FROM 后衔接的内容
     *     3. Joins 为 JOIN 后衔接的内容
     *     4. Where 为 WHERE 后衔接的内容
     * </pre>
     *
     * @param plainSelect     PlainSelect 独立查询语句
     * @param outerAliasTable 上级表别名
     * @param workbench       工作台
     */
    private void handlePlainSelect(PlainSelect plainSelect, String outerAliasTable, Workbench workbench) {
        workbench.digIn();
        handleSelectItems(plainSelect.getSelectItems(), outerAliasTable, workbench);
        handleFrom(plainSelect.getFromItem(), workbench);
        handleJoin(plainSelect.getJoins(), workbench);
//        handleWhere(plainSelect.getWhere(), workbench);
        workbench.digOut();
    }

    private void handleSetOperationList(SetOperationList setOperationList, Workbench workbench) {
        handleSetOperationList(setOperationList, null, workbench);
    }

    private void handleSetOperationList(SetOperationList setOperationList, String outerAliasTable, Workbench workbench) {
        System.out.println("暂不支持 INTERSECT, EXCEPT, MINUS, UNION 逻辑");
    }

    /**
     * <pre>
     *     SelectItems 为查询的字段列表，需要考虑的情况如下:
     *     1. Column 最小粒度的查询字段
     *     2. AllColumns 表示所有字段
     *     3. BinaryExpression 字段与字段之间通过如 Addition 等情况拼接
     *     4. Function 通过函数进行组合
     * </pre>
     *
     * @param selectItems     查询字段列表
     * @param outerAliasTable 上级表别名
     * @param workbench       工作台
     */
    private void handleSelectItems(List<SelectItem<?>> selectItems, String outerAliasTable, Workbench workbench) {
        for (SelectItem<?> selectItem : selectItems) {
            Expression expression = selectItem.getExpression();
            String aliasName = Optional.ofNullable(selectItem.getAlias()).orElse(new Alias(null)).getName();
            if (expression instanceof Column column) {
                handleColumn(column, null, aliasName, false, outerAliasTable, workbench);
            } else if (expression instanceof AllColumns) {
                handleAllColumns(aliasName, outerAliasTable, workbench);
            } else if (expression instanceof BinaryExpression binary) {
                handleBinaryExpression(binary, expression.toString(), aliasName, outerAliasTable, workbench);
            } else if (expression instanceof Function function) {
                handleFunction(function, expression.toString(), aliasName, outerAliasTable, workbench);
            }
        }
    }

    /**
     * <pre>
     *     FromItem 为 FROM 后衔接的内容，需要考虑的情况如下:
     *     1. Table 直接衔接表名
     *     2. ParenthesedFromItem 衔接通过括号包含的内容
     *     3. Select 衔接子查询
     * </pre>
     *
     * @param fromItem  FROM 后衔接的内容
     * @param workbench 工作台
     */
    private void handleFrom(FromItem fromItem, Workbench workbench) {
        // From 后衔接表名（最简单的情况）
        if (fromItem instanceof Table table) {
            workbench.updateTable(table.getName(), Optional.ofNullable(table.getAlias()).orElse(new Alias(null)).getName());
        }
        // From 后衔接括号，要根据括号内部的表达式进行处理
        else if (fromItem instanceof ParenthesedFromItem) {
            // 获取括号内的 FromItem
            // TODO
            FromItem innerFromItem = ((ParenthesedFromItem) fromItem).getFromItem();

        }
        // From 后衔接子查询（仅支持 PlainSelect）
        else if (fromItem instanceof Select select) {
            // 获取括号内的 FromItem
            String outerAliasTable = Optional.ofNullable(fromItem.getAlias()).orElse(new Alias(null)).getName();
            workbench.updateTable(select.toString(), outerAliasTable);
            handleSelect(select, outerAliasTable, workbench);
        }
    }

    /**
     * <pre>
     *     Join 为  JOIN, LEFT JOIN 等词后衔接的内容，需要考虑的情况如下:
     *     1. Table 直接衔接表名
     *     2. RightItem 衔接子查询语句
     * </pre>
     *
     * @param joins
     * @param workbench
     */
    private void handleJoin(List<Join> joins, Workbench workbench) {
        if (joins == null) {
            return;
        }
        for (Join join : joins) {
            // 获取 JOIN 的表名
            FromItem rightItem = join.getRightItem();
            if (rightItem instanceof Table table) {
                workbench.updateTable(table.getName(), Optional.ofNullable(table.getAlias()).orElse(new Alias(null)).getName());
            }
            // 对于如下情况 left join (select ... from ...) t 进行处理
            else if (rightItem instanceof Select select) {
                // 类似于 From 的处理
                String outerAliasTable = Optional.ofNullable(rightItem.getAlias()).orElse(new Alias(null)).getName();
                workbench.updateTable(select.toString(), outerAliasTable);
                handleSelect(select, outerAliasTable, workbench);
            }
            // TODO 获取 JOIN 的条件，目前可以不处理
//            Expression expression = join.getOnExpression();
//            handleWhere(expression, workbench);
        }
    }

//    private void handleWhere(Expression expression, Workbench workbench) {
//
//    }

    /**
     * <pre>
     *     Function 为函数，需要考虑的情况如下:
     *     1. Column 最小粒度的查询字段
     *     2. BinaryExpression 字段与字段之间通过如 Addition 等情况拼接
     *     3. Function 通过函数进行组合
     * </pre>
     *
     * @param function        函数
     * @param fieldName       字段名
     * @param aliasName       别名
     * @param outerAliasTable 上级表别名
     * @param workbench       工作台
     */
    private void handleFunction(Function function, String fieldName, String aliasName, String outerAliasTable, Workbench workbench) {
        ExpressionList<?> expressions = function.getParameters();
        for (Expression expression : expressions) {
            if (expression instanceof Column column) {
                handleColumn(column, fieldName, aliasName, true, outerAliasTable, workbench);
            } else if (expression instanceof BinaryExpression binary) {
                handleBinaryExpression(binary, fieldName, aliasName, outerAliasTable, workbench);
            } else if (expression instanceof Function subFunction) {
                handleFunction(subFunction, fieldName, aliasName, outerAliasTable, workbench);
            }
        }
    }

    private void handleBinaryExpression(BinaryExpression binary, String fieldName, String aliasName, String outerAliasTable, Workbench workbench) {
        processBinaryRecursive(binary, fieldName, aliasName, outerAliasTable, workbench);
    }

    /**
     * <pre>
     *     递归处理二元表达式
     * </pre>
     *
     * @param expression      表达式
     * @param fieldName       字段
     * @param aliasName       别名
     * @param outerAliasTable 上级表别名
     * @param workbench       工作台
     */
    private void processBinaryRecursive(Expression expression, String fieldName, String aliasName, String outerAliasTable, Workbench workbench) {
        if (expression instanceof BinaryExpression binary) {
            processBinaryRecursive(binary.getLeftExpression(), fieldName, aliasName, outerAliasTable, workbench);
            processBinaryRecursive(binary.getRightExpression(), fieldName, aliasName, outerAliasTable, workbench);
        } else if (expression instanceof Column column) {
            handleColumn(column, fieldName, aliasName, true, outerAliasTable, workbench);
        } else if (expression instanceof Function function) {
            handleFunction(function, fieldName, aliasName, outerAliasTable, workbench);
        }
    }

    /**
     * <pre>
     *     Column 表示字段，需要考虑的情况如下:
     *     1. 未传入表别名，且字段有标注表名，使用字段对应表名
     *     2. 未传入字段名，使用列名
     *     3. Column 作为一个大的字段中的一部分，以 Parameter 的形式添加进字段中，后续进行整合
     * </pre>
     *
     * @param column          字段对象
     * @param fieldName       字段名
     * @param aliasName       别名
     * @param isParameter     是否为参数
     * @param outerAliasTable 上级表别名
     * @param workbench       工作台
     */
    private void handleColumn(Column column, String fieldName, String aliasName, boolean isParameter, String outerAliasTable, Workbench workbench) {
        // 未传入表别名，且字段有标注表名，使用字段对应表名
        String innerAliasTable = null;
        Table table = column.getTable();
        if (table != null) {
            innerAliasTable = table.getName();
        }
        // 未传入字段名，使用列名
        if (fieldName == null) {
            fieldName = column.getColumnName();
        }
        String parameterName = (isParameter) ? column.getColumnName() : null;
        workbench.addField(fieldName, aliasName, parameterName, null, innerAliasTable, outerAliasTable);
    }

    /**
     * <pre>
     *     AllColumns 表示 * 修饰的所有字段，需要新增特殊 Field，且无需处理上级表别名
     * </pre>
     *
     * @param aliasName
     * @param innerAliasTable
     * @param workbench
     */
    private void handleAllColumns(String aliasName, String innerAliasTable, Workbench workbench) {
        workbench.addField("*", aliasName, null, null, innerAliasTable, null);
    }
}
